package corewarsxxi.gui;

import corewarsxxi.logic.*;
import corewarsxxi.logic.Core.SimulationResults;
import corewarsxxi.logic.throwables.simulationOver;
import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 *
 * @author ei10139
 */
public final class Debugger extends JFrame
{
    private static final Icon iStar = new ImageIcon("img/about_star.png");
    private static final Icon iStarActive = new ImageIcon("img/about_star_active.png");

    Canvas canv;
    DebuggerListFrame dlf;
    private cycleThread cThread = new cycleThread(this);
    private JList listGameStatus, listGameWarriors;
    private DefaultListModel listGameStatusModel, listGameWarriorsModel;

    private final JButton but, butRS, butR, butRF, butI;

    private ArrayList<Warrior> warriorsLoaded = new ArrayList<Warrior>();

    public Debugger()
    {
        super("Core Wars XXI - Simulator");

        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (Exception e) {
        }

        JMenuBar menuBar = new JMenuBar();
            final JMenu jmWarrior = new JMenu("Main Menu");
            jmWarrior.setMnemonic(KeyEvent.VK_M);

                // -------------------- Open Warrior Maker --------------------

                final JMenuItem jmiWarriorWarriorMaker = new JMenuItem("Open Warrior Maker", WarriorMaker.iWarriorNew);
                jmiWarriorWarriorMaker.setMnemonic(KeyEvent.VK_W);
                jmiWarriorWarriorMaker.setAccelerator(KeyStroke.getKeyStroke(
                    KeyEvent.VK_W, ActionEvent.ALT_MASK));
                jmiWarriorWarriorMaker.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        new WarriorMaker();
                    }
                });
                
                // -------------------- LOAD --------------------
                final JMenuItem jmiWarriorLoad = new JMenuItem("Load Warrior", WarriorMaker.iWarriorLoad);
                jmiWarriorLoad.setMnemonic(KeyEvent.VK_L);
                jmiWarriorLoad.setAccelerator(KeyStroke.getKeyStroke(
                    KeyEvent.VK_L, ActionEvent.ALT_MASK));
                jmiWarriorLoad.addActionListener(alWarriorLoad);

                // -------------------- CLOSE --------------------
                final JMenuItem jmiWarriorClose = new JMenuItem("Close", WarriorMaker.iClose);
                jmiWarriorClose.setMnemonic(KeyEvent.VK_C);
                jmiWarriorClose.setAccelerator(KeyStroke.getKeyStroke(
                    KeyEvent.VK_C, ActionEvent.ALT_MASK));

                jmiWarriorClose.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                            Debugger.this.dispose();
                    }
                });
                jmiWarriorClose.addMouseListener(new MouseListener() {
                    public void mouseClicked(MouseEvent e) {}
                    public void mousePressed(MouseEvent e) {}
                    public void mouseReleased(MouseEvent e) {}
                    public void mouseEntered(MouseEvent e) {
                        jmiWarriorClose.setIcon(WarriorMaker.iCloseActive);
                    }
                    public void mouseExited(MouseEvent e) {
                        jmiWarriorClose.setIcon(WarriorMaker.iClose);
                    }
                });
            jmWarrior.add(jmiWarriorWarriorMaker);
            jmWarrior.addSeparator();
            jmWarrior.add(jmiWarriorLoad);
            jmWarrior.addSeparator();
            jmWarrior.add(jmiWarriorClose);


            JMenu jmHelp = new JMenu("Help");
            jmHelp.setMnemonic(KeyEvent.VK_H);
                final JMenuItem jmiHelpOnlineSupport = new JMenuItem("Online Help & Support", WarriorMaker.iHelp);
                jmiHelpOnlineSupport.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        try {
                            Desktop.getDesktop().browse(new URI("http://vyznev.net/corewar/guide.html"));
                        } catch (Exception ex) {
                            JOptionPane.showMessageDialog(Debugger.this,
                                "Error openning browser:\n" + ex.getMessage(),
                                "Error", JOptionPane.ERROR_MESSAGE);
                        }
                    }
                });

                final JMenuItem jmiHelpAbout = new JMenuItem("About Core Wars XXI", iStar);
                jmiHelpAbout.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    JOptionPane.showMessageDialog(Debugger.this,
                            "This game was proudly coded by\n" +
                            "Luís Fonseca, ei10139 @ FEUP.",
                            "About Core Wars XXI",
                            JOptionPane.INFORMATION_MESSAGE);
                    }
                });
                jmiHelpAbout.addMouseListener(new MouseListener() {
                    public void mouseClicked(MouseEvent e) {}
                    public void mousePressed(MouseEvent e) {}
                    public void mouseReleased(MouseEvent e) {}
                    public void mouseEntered(MouseEvent e) {
                        jmiHelpAbout.setIcon(iStarActive);
                    }
                    public void mouseExited(MouseEvent e) {
                        jmiHelpAbout.setIcon(iStar);
                    }
                });

            jmHelp.add(jmiHelpOnlineSupport);
            jmHelp.addSeparator();
            jmHelp.add(jmiHelpAbout);

        menuBar.add(jmWarrior);
        menuBar.add(jmHelp);
        super.setJMenuBar(menuBar);

        JPanel panel = new JPanel();
        GroupLayout layout = new GroupLayout(panel);

        dlf = new DebuggerListFrame();
        canv = new Canvas(this);
        JLabel jlCoreSize = new JLabel("Core Size: ");
        jlCoreSize.setFont(new Font("Segoe UI", Font.PLAIN, 12));
        final JComboBox jcCoreSize = new JComboBox(
                                    new String[]{"800", "8000", "8192", "55400"});
        jcCoreSize.setSelectedIndex(1);
        jcCoreSize.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                Core.getInstance().getCoreConfig()
                        .setCoreSize(Integer.valueOf((String)jcCoreSize.getSelectedItem()));
                canv.resize();
            }
        });

        JLabel jlMaxCycles = new JLabel("Max cycles: ");
        jlMaxCycles.setFont(new Font("Segoe UI", Font.PLAIN, 12));
        final JComboBox jcMaxCycles = new JComboBox(
                                    new String[]{"8000", "20000", "50000", "200000", "500000"});
        jcMaxCycles.setSelectedIndex(1);
        jcMaxCycles.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                Core.getInstance().getCoreConfig()
                        .setMaxCycles(Integer.valueOf((String)jcMaxCycles.getSelectedItem()));
            }
        });

        JLabel jlMinDistance = new JLabel("Min distance: ");
        jlMinDistance.setFont(new Font("Segoe UI", Font.PLAIN, 12));
        final JComboBox jcMinDistance = new JComboBox(
                                    new String[]{"50", "100", "200", "500"});
        jcMinDistance.setSelectedIndex(1);
        jcMinDistance.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                Core.getInstance().getCoreConfig()
                        .setMinDistance(Integer.valueOf((String)jcMinDistance.getSelectedItem()));
            }
        });
        but = new JButton("Cycle");
        butRS = new JButton("Run Slow");
        butR = new JButton("Run");
        butRF = new JButton("Run Fast");
        butI = new JButton("Instant");

        butI.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                cThread.kill();
                cThread = new cycleThread(Debugger.this);
                Debugger.this.simulationEnded(Core.getInstance().performFullSimulation());
            }
        });

        JButton butSGS = new JButton("Start New Game Simulation");
        butSGS.setFont(new Font("Segoe UI", Font.BOLD, 12));
        butSGS.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if(warriorsLoaded.size() < 2)
                {
                    JOptionPane.showMessageDialog(Debugger.this,
                            "Insufficient Warriors Loaded.",
                            "Error",
                            JOptionPane.ERROR_MESSAGE);
                    return;
                }
                
                but.setText("Cycle");
                cThread.kill();
                cThread = new cycleThread(Debugger.this);
                controlButtonsSetEnabled(true);
                canv.reset();
                Core.getInstance().reset();
                Core.getInstance().loadWarriors(warriorsLoaded.toArray(new Warrior[0]));
                listGameStatusModel.addElement(
                        "The simulation is now ready.");
            }
        });
        
        controlButtonsSetEnabled(false);


        listGameWarriorsModel = new DefaultListModel();

        listGameWarriors = new JList(listGameWarriorsModel);
        listGameWarriors.setFont(new Font("Calibri", Font.PLAIN, 13));

        final JScrollPane spListGameWarriors =
                new JScrollPane(listGameWarriors,
                    ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        JPanel jpGameConfig = new JPanel(new BorderLayout());

        JPanel jpGameConfigButtons = new JPanel();
        GroupLayout layoutGameConfigButtons = new GroupLayout(jpGameConfigButtons);
        JButton jbGameConfigLoad = new JButton("Load Warrior", WarriorMaker.iWarriorLoad);
        final JButton jbGameConfigDelete = new JButton("Remove Warrior", WarriorMaker.iDelete);

        jbGameConfigLoad.addActionListener(alWarriorLoad);
        jbGameConfigDelete.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int index = listGameWarriors.getSelectedIndex();
                if(index == -1)
                    return;

                listGameStatusModel.addElement(
                        "Unloaded Warrior '" + warriorsLoaded.get(index) + "'..");
                warriorsLoaded.remove(index);
                listGameWarriorsModel.removeElementAt(index);
                jbGameConfigDelete.setEnabled(false);
                controlButtonsSetEnabled(false);
                listGameWarriors.repaint();
            }
        });
        jbGameConfigDelete.setEnabled(false);
        listGameWarriors.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                int index = listGameWarriors.getSelectedIndex();
                if(index == -1)
                    return;
                
                jbGameConfigDelete.setEnabled(true);
            }
        });

        jbGameConfigLoad.setPreferredSize(new Dimension(140, 20));
        jbGameConfigDelete.setPreferredSize(new Dimension(140, 20));

        layoutGameConfigButtons.linkSize(SwingConstants.HORIZONTAL, jbGameConfigLoad);
        layoutGameConfigButtons.linkSize(SwingConstants.HORIZONTAL, jbGameConfigDelete);

        layoutGameConfigButtons.setAutoCreateGaps(true);
        layoutGameConfigButtons.setVerticalGroup(layoutGameConfigButtons.createSequentialGroup()
                .addGap(30)
                .addComponent(jbGameConfigLoad)
                .addComponent(jbGameConfigDelete));
        layoutGameConfigButtons.setHorizontalGroup(layoutGameConfigButtons.createParallelGroup()
                .addComponent(jbGameConfigLoad)
                .addComponent(jbGameConfigDelete));
        jpGameConfigButtons.setPreferredSize(new Dimension(150, 50));


        jpGameConfigButtons.setLayout(layoutGameConfigButtons);

        jpGameConfig.add(spListGameWarriors, BorderLayout.CENTER);
        jpGameConfig.add(jpGameConfigButtons, BorderLayout.WEST);


        jpGameConfig.setFont(new Font("Segoe UI", Font.PLAIN, 13));
        jpGameConfig.setBorder(BorderFactory.createTitledBorder("Game Configuration"));
        listGameStatusModel = new DefaultListModel();
        listGameStatusModel.addElement("Core Wars XXI is loading.");
        listGameStatus = new JList(listGameStatusModel);

        listGameStatus.setFont(new Font("Calibri", Font.PLAIN, 13));
        listGameStatusModel.addListDataListener(new ListDataListener() {
            public void intervalAdded(ListDataEvent e) {
                listGameStatus.repaint();
                listGameStatus.setSelectedIndex(listGameStatusModel.getSize() - 1);
            }
            public void intervalRemoved(ListDataEvent e) {
                listGameStatus.repaint();
                listGameStatus.setSelectedIndex(listGameStatusModel.getSize() - 1);
            }
            public void contentsChanged(ListDataEvent e) {
                listGameStatus.repaint();
                listGameStatus.setSelectedIndex(listGameStatusModel.getSize() - 1);
            }
        });

        final JScrollPane spListGameStatus =
                new JScrollPane(listGameStatus,
                    ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        JPanel jpListGameStatus = new JPanel(new BorderLayout());
        jpListGameStatus.add(spListGameStatus, BorderLayout.CENTER);
        jpListGameStatus.setFont(new Font("Segoe UI", Font.PLAIN, 13));
        jpListGameStatus.setBorder(BorderFactory.createTitledBorder("Game Status"));


        layout.setAutoCreateGaps(true);
        layout.setAutoCreateContainerGaps(true);
        layout.setHorizontalGroup(
            layout.createParallelGroup()
                .addGroup(layout.createSequentialGroup()
                    .addComponent(butSGS)
                    .addComponent(but)
                    .addComponent(butRS)
                    .addComponent(butR)
                    .addComponent(butRF)
                    .addComponent(butI)
                    .addGap(16)
                    .addComponent(jlCoreSize)
                    .addComponent(jcCoreSize)
                    .addComponent(jlMaxCycles)
                    .addComponent(jcMaxCycles)
                    .addComponent(jlMinDistance)
                    .addComponent(jcMinDistance))
                .addGroup(layout.createSequentialGroup()
                    .addComponent(dlf)
                    .addGroup(layout.createParallelGroup()
                        .addComponent(jpGameConfig)
                        .addComponent(canv)
                        .addComponent(jpListGameStatus)))
        );
        layout.setVerticalGroup(layout.createParallelGroup()
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(Alignment.BASELINE)
                    .addComponent(butSGS)
                    .addComponent(but)
                    .addComponent(butRS)
                    .addComponent(butR)
                    .addComponent(butRF)
                    .addComponent(butI)
                    .addComponent(jlCoreSize)
                    .addComponent(jcCoreSize)
                    .addComponent(jlMaxCycles)
                    .addComponent(jcMaxCycles)
                    .addComponent(jlMinDistance)
                    .addComponent(jcMinDistance))
            .addGroup(layout.createParallelGroup()
                .addComponent(dlf)
                .addGroup(layout.createSequentialGroup()
                    .addComponent(jpGameConfig, 150, 150, 150)
                    .addComponent(canv)
                    .addComponent(jpListGameStatus))))
        );
        //layout.linkSize(SwingConstants.VERTICAL, jpListGameStatus);
        layout.linkSize(SwingConstants.HORIZONTAL, dlf);

        panel.setLayout(layout);
        super.setMinimumSize(new Dimension(980, 738));
        super.getContentPane().add(panel);
        super.pack();
        super.setVisible(true);
        super.setLocationRelativeTo(null);
        super.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        but.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    if(but.getText().equals("Cycle"))
                    {
                        Core.getInstance().cycle();
                    }
                    else
                    {
                        but.setText("Cycle");
                        cThread.kill();
                        cThread = new cycleThread(Debugger.this);
                    }
                } catch (simulationOver ex) {
                    Debugger.this.simulationEnded(ex.getSimulationResults());
                }
            }
        });

        butRS.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                but.setText("Pause");
                cThread.setSpeed(cycleThread.speed.SLOW);
                cThread.start();
            }
        });

        butR.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                but.setText("Pause");
                cThread.setSpeed(cycleThread.speed.NORMAL);
                cThread.start();
            }
        });

        butRF.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                but.setText("Pause");
                cThread.setSpeed(cycleThread.speed.ULTRAFAST);
                cThread.start();
            }
        });

        Core.getInstance().memArray.addObserver(canv);
        Core.getInstance().memArray.addObserver(dlf);
        Core.getInstance().addWarriorDeathListener(new IWarriorDeathListener() {
            public void handle(Warrior w) {
                listGameStatusModel.addElement("Warrior '" + w.getName() + "' has died!");
            }
        });
        super.setIconImage(new ImageIcon("img/warrior_new.png").getImage());
    }

    private void simulationEnded(SimulationResults sr) {
        listGameStatusModel.addElement("End of simulation.");
        if(sr.hasWinner())
            listGameStatusModel.addElement("The winner was " + sr.getWinner());
        else
            listGameStatusModel.addElement("It was a tie between: " + Arrays.toString(sr.getStillAlive()));

    }
    
    static class cycleThread extends Thread
    {
        public static enum speed { SLOW, NORMAL, FAST, ULTRAFAST };

        private int sleepTime = 100;
        private boolean stop = true;

        Debugger deb;

        public cycleThread(Debugger deb)
        {
            this.deb = deb;
        }

        @Override
        public void start()
        {
            if(!stop)
                return;
            
            stop = false;
            super.start();
        }

        @Override
        public void run()
        {
            try {
                while(!stop)
                {
                    try {
                        Core.getInstance().cycle();
                    } catch (simulationOver ex) {
                        deb.simulationEnded(ex.getSimulationResults());
                        deb.but.setText("Cycle");
                        //JOptionPane.showMessageDialog(Debugger.this, "Simulation Ended.");
                        return;
                    }
                    Thread.sleep(sleepTime);
                }
            } catch (InterruptedException ex) {
            }
        }

        public void setSpeed(speed s)
        {
            switch(s)
            {
                case SLOW:
                    sleepTime = 100; return;
                case NORMAL:
                    sleepTime = 40; return;
                case FAST:
                    sleepTime = 18; return;
                case ULTRAFAST:
                    sleepTime = 2; return;
            }
        }

        public void kill()
        {
            stop = true;
        }
    }

    public void controlButtonsSetEnabled(boolean e)
    {
        but.setEnabled(e);
        butRS.setEnabled(e);
        butR.setEnabled(e);
        butRF.setEnabled(e);
        butI.setEnabled(e);
    }

    public boolean updateDebuggerOffset(int offset)
    {
        if(offset < 0 || offset > Core.getInstance().getSize())
            return false;

        dlf.setOffset(offset);
        return true;
    }

    ActionListener alWarriorLoad = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            JFileChooser fc = new JFileChooser();
            fc.setFileFilter(WarriorMaker.fcRedCodeWarriorFilter);
            if(fc.showOpenDialog(Debugger.this) != JFileChooser.APPROVE_OPTION)
                return;

            Warrior w = new Warrior("");
            try {
                w.load(fc.getSelectedFile().getPath());
            } catch (FileNotFoundException ex) {
                JOptionPane.showMessageDialog(Debugger.this,
                        "File not found.:",
                        "Error", JOptionPane.ERROR_MESSAGE);
                return;
            } catch (ClassNotFoundException ex) {
                JOptionPane.showMessageDialog(Debugger.this,
                        "Warrior seems to be corrupt or was saved in a older version of this program.",
                        "Error", JOptionPane.ERROR_MESSAGE);
                return;
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(Debugger.this,
                        "Error reading the file:\n" + ex.getMessage(),
                        "Error", JOptionPane.ERROR_MESSAGE);
                return;
            }

            warriorsLoaded.add(w);
            listGameWarriorsModel.addElement(w);
            listGameStatusModel.addElement(
                    "Loaded Warrior '" + w.getName() + "'..");

            listGameWarriors.repaint();
        }
    };

    public static void main(String[] args) throws InterruptedException
    {
        Debugger debugger = new Debugger();
        Thread.sleep(1000);
        debugger.listGameStatusModel.addElement("Core Wars XXI is ready.");
    }
}
